/*
 -- IOTA Crypto Core
 --
 -- 2018 by Thomas Pototschnig <microengineer18@gmail.com>
 -- discord: pmaxuw#8292
 -- https://gitlab.com/iccfpga-rv
 --
 -- Permission is hereby granted, free of charge, to any person obtaining
 -- a copy of this software and associated documentation files (the
 -- "Software"), to deal in the Software without restriction, including
 -- without limitation the rights to use, copy, modify, merge, publish,
 -- distribute, sublicense, and/or sell copies of the Software, and to
 -- permit persons to whom the Software is furnished to do so, subject to
 -- the following conditions:
 --
 -- The above copyright notice and this permission notice shall be
 -- included in all copies or substantial portions of the Software.
 --
 -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 -- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 -- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 -- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 -- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 -- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 -- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWAR
 */

#include <stdint.h>
#include <string.h>

#include "iota/consts.h"
#include "iota/iota_types.h"



int decrement_tryte(int max, tryte_t *tryte)
{
    const int slack = *tryte - MIN_TRYTE_VALUE;
    if (slack <= 0) {
        return 0;
    }

    const int dec = MIN(max, slack);
    *tryte -= dec;

    return dec;
}

int increment_tryte(int max, tryte_t *tryte)
{
    const int slack = MAX_TRYTE_VALUE - *tryte;
    if (slack <= 0) {
        return 0;
    }

    const int inc = MIN(max, slack);
    *tryte += inc;

    return inc;
}


static void normalize_hash_fragment(tryte_t *fragment_trytes)
{
    int sum = 0;
    for (unsigned int j = 0; j < 27; j++) {
        sum += fragment_trytes[j];
    }

    for (unsigned int j = 0; j < 27; j++) {
        if (sum > 0) {
            sum -= decrement_tryte(sum, &fragment_trytes[j]);
        }
        else if (sum < 0) {
            sum += increment_tryte(-sum, &fragment_trytes[j]);
        }
        if (sum == 0) {
            break;
        }
    }
}

bool normalize_hash(tryte_t *hash_trytes, int secLevel)
{
	if (!hash_trytes)
		return false;

	bool valid = true;

	for (int i = 0; i < secLevel; i++) {
        normalize_hash_fragment(hash_trytes + i * 27);

    	for (int j = 0; j < 27; j++) {
    		if (hash_trytes[i*27+j] == 13) {
    			valid = false;
    		}
    	}
    }
	return valid;
}

bool validateTrytes(const char* trytes, int len, bool checkzero = true) {
	if (!trytes)
		return false;
	for (int i = 0; i < len; i++) {
		if (trytes[i] != '9' && (trytes[i] < 'A' || trytes[i] > 'Z')) {
			return false;
		}
	}
	if (checkzero && trytes[len])	// check zero terminator
		return false;
	return true;
}

// eclipse complains it doesn't now strnlen ...
// ... so define it here as prototype
size_t strnlen(const char *s, size_t maxlen);

bool validateTrytesMaxLen(const char* trytes, int maxLen, bool checkzero = true) {
	return validateTrytes(trytes, strnlen(trytes, maxLen), checkzero);
}

bool validateHex(const char* hex, int len, bool checkzero = true) {
	if (!hex)
		return false;

	for (int i = 0; i < len; i++) {
		if ((hex[i] >= '0' && hex[i] <= '9') || (hex[i] >='a' && hex[i] <= 'f')) {
			continue;
		} else {
			return false;
		}
	}
	if (checkzero && hex[len])	// check zero terminator
		return false;
	return true;
}

bool validateBase64(const char* base64, int len, bool checkzero = true) {
	if (!base64)
		return false;

	for (int i = 0; i < len; i++) {
		if ((base64[i] >= 'A' && base64[i] <= 'Z') ||
			(base64[i] >= 'a' && base64[i] <= 'z') ||
			(base64[i] >= '0' && base64[i] <= '9') ||
			(base64[i] == '+') ||
			(base64[i] == '=') ||
			(base64[i] == '/')) {
			continue;
		} else {
			return false;
		}
	}
	if (checkzero && base64[len])	// check zero terminator
		return false;
	return true;
}

bool hexToBytes(const char* key, uint8_t* bytes, int bytesLength) {
	if (!key)
		return false;
	if (!bytes)
		return false;


	const char* keyPtr = key;
	for (int i=0;i<bytesLength;i++) {
		uint8_t val = 0;
		for (int j=0;j<2;j++) {
			val <<= 4;
			if (*keyPtr >= '0' && *keyPtr <= '9') {
				val |= *keyPtr - 48;
			} else if (*keyPtr >= 'a' && *keyPtr <= 'f') {
				val |= *keyPtr - 87;

			} else {
				return false;
			}
			keyPtr++;
		}
		bytes[i] = val;
	}
	return true;
}

bool asciiToTrytes(const char* src, char* dst, int bufferSize) {
	if (!src)
		return false;
	if (!dst)
		return false;


	int slen = strnlen(src, bufferSize/2);

	if (slen > bufferSize/2) {
		return false;
	}

	for (int i=0;i<slen;i++) {
		char c = *src++;
		uint8_t tryte;
		for (int j=0;j<2;j++) {
			if (!j)
				tryte = (uint8_t) c % 27;
			else
				tryte = (uint8_t) c / 27;
			if (!tryte) {
				*dst++ = '9';
			} else {
				*dst++ = 'A' + (tryte-1);
			}
		}
	}
	return true;
}
// example: "44'/4218'/0'/0'"
#define STATE_INT		0
#define STATE_SLASH		1
#define STATE_TICK		2
#define STATE_ZERO		3

// state-machine for parsing bip-path
bool validateBIPPath(const char* p, int maxDepth, uint32_t* out) {
	int depth = 0;
	int state = STATE_INT;
	int tmp = 0;
	int ctr = 0;

	while (1) {
		ctr++;
		if (ctr >= 32) {
			return false;
		}
		switch (state) {
		case STATE_INT:
			switch(*p) {
			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
			case '8':
			case '9':
				tmp *= 10;
				tmp += *p++ - 48;
				break;
			case '/':
				p++;
				state = STATE_SLASH;
				break;
			case '\'':
				p++;
				tmp |= 0x80000000;
				state = STATE_TICK;
				break;
			case 0:
				state = STATE_ZERO;
				continue;
			default:
				return false;
			}
			break;
		case STATE_TICK:
			switch (*p) {
			case '/':
				p++;
				state = STATE_SLASH;
				break;
			case 0:
				state = STATE_ZERO;
				continue;
			default:
				return false;
			}
			break;;
		case STATE_SLASH:
			out[depth] = tmp;
			tmp = 0;
			depth++;
			if (depth >= maxDepth)
				return false;
			state = STATE_INT;
			break;
		case STATE_ZERO:
			out[depth] = tmp;
			tmp = 0;
			depth++;

			if (depth >= maxDepth)
				return true;
			else
				return false;
			break;
		default:
			state = STATE_INT;
		}
	}
}

bool validateString(const char* s) {
	for (uint32_t i=0;i<strlen(s);i++) {
		if (
			(s[i] >= 'a' && s[i] <= 'z') ||
			(s[i] >= 'A' && s[i] <= 'Z')
		) {
			continue;
		} else {
			return false;
		}
	}
	return true;
}





